#include <vector>
#include <iostream>
#include <cstdlib>

#ifndef MATRIX
#define MATRIX

#define UNDEFINED -1

template<typename T> 
class Matrix 
{
  private:
    std::vector<std::vector<T> > data;   

  public:  
    /*********************************
        C O N S T R U C T O R S
    **********************************/
    Matrix(){
      data.empty();
      std::clog << "Matrix construction" << std::endl;
    };
    
    Matrix(int swidth, int sheight) {
      data.resize(0);
      if(swidth > 0 && sheight > 0) {
        data.resize(swidth);
        for(unsigned int i = 0; i < data.size(); i++) {
          data[i].resize(sheight);
        }
        std::clog << "Matrix construction" << std::endl;
      }
    };

    Matrix(int size){
      if(size > 0) {
        data.resize(size);
        for(unsigned int i = 0; i < data.size(); i++) {
          data[i].resize(size);
        }
      } else {
        data.resize(0);
      }
      std::clog << "Matrix construction" << std::endl;
    };

    /*********************************
         M A T H E M A T I C S
    **********************************/
    Matrix<T> product(Matrix<T> & m2) {
      Matrix<T> m3(getHeight(), m2.getWidth());
      if(this->getWidth() != m2.getHeight()) {
        return m3.data.empty();
      }
      unsigned int height1 = data.size() == 0 ? 0 : data[0].size();
      unsigned int width1 = this->data.size();
      unsigned int width2 = m2.getWidth();
      
      for(unsigned int i = 0; i < height1; i++) {
        for(unsigned int j = 0; j < width2; j++) {
          for(unsigned int k = 0; k < width1; k++) {
            m3.data[i][j] += this->data[i][k] * m2.getCoef(k, j);
          }
        }
      }      
      return m3;
    };

    /*********************************
         M A N I P U L A T I O N S
    **********************************/
    bool addRow(T value = 0) {
      std::vector<T> newRow;
      data.resize(data.size() + 1);
      newRow.resize(data.size() == 0 ? 0 : data[0].size(), value);
      data.back() = newRow;
      std::clog << "Row added" << std::endl;
      return true;
    };
  
    bool addColumn(T value = 0) {
      for(unsigned int i = 0; i < data.size(); i++) {
        data[i].resize(data[i].size() + 1, value);      
      }
      std::clog << "Column added" << std::endl;
      return true;
    };    
  
    /*********************************
              S E T T E R S
    **********************************/
    bool setCoef(unsigned int i, unsigned int j, T val) {
      if(data.size() != 0 && i < data.size() && j < data[0].size()) {
        data[i][j] = val;
        return true;
      }
      std::clog << "Index out of range" << std::endl;
      return false;
    }

    /*********************************
              G E T T E R S
    **********************************/
    int getWidth() { 
      return data.size(); 
    };

    int getHeight() { 
      return data.size() == 0 ? 0 : data[0].size(); 
    };

    T getCoef(unsigned int i, unsigned int j) {
      if(data.size() != 0 && i < data.size() && j < data[0].size()) {
        return data[i][j];
      }
      std::clog << "Index out of range" << std::endl;
      return UNDEFINED;
    };
      
    T getMax() {
      unsigned int height = getHeight();
      T max = getCoef(0, 0);
      for(unsigned int i = 0; i < data.size(); i ++) {
        for(unsigned int j = 0; j < height; j++) {
          if(max < data[i][j]) {
            max = data[i][j];
          }
        }
      }
      return max;
    };

    T getMin() {
      unsigned int height = getHeight();
      T min = getCoef(0, 0);
      for(unsigned int i = 0; i < data.size(); i ++) {
        for(unsigned int j = 0; j < height; j++) {
          if(min > data[i][j]) {
            min = data[i][j];
          }
        }
      }
      return min;
    };

    T getSum() {
      unsigned int height = getHeight();
      T sum = 0;
      for(unsigned int i = 0; i < data.size(); i++) {
        for(unsigned int j = 0; j < height; j++) {
          sum += data[i][j];
        }
      }
      return sum;
    };

    /*********************************
              O T H E R
    **********************************/
    void print(){  
      unsigned int height = getHeight();
      for(unsigned int i = 0; i < data.size(); i++) {
          for (unsigned int j = 0; j < height; j++) {
            std::cout << data[i][j] << " ";
          }
          std::cout << std::endl;
       }
    };
    
    void info() {
      std::cout << "Width = " << getHeight() << " height = "<< data.size() << " max = " << getMax() << " min = "<< getMin() << " sum = " << getSum() << std::endl; 
    };

    void random(int max = 9999){
      srand ( time(NULL) );
      unsigned int height = getHeight();
      for(unsigned int i = 0; i < data.size(); i++) {
        for(unsigned int j = 0; j < height; j++) {
          data[i][j] = std::rand() % (max + 1);
        }
      }
    };

    /*********************************
         D E S T R U C T O R
    **********************************/    
    ~Matrix() {
      data.resize(0);
      std::clog << "Matrix destruction" << std::endl;
    };
};

template<typename T> 
Matrix<T> operator*(Matrix<T> const& m1, Matrix<T> const& m2) {
    return m1.product(m2);
}
#endif
